package serverCommunication;

import infrastructure.KVMessageImp;
import infrastructure.LoggingManager;
import infrastructure.MarshallerKvMessage;

import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import javax.xml.bind.JAXBException;
import org.apache.log4j.*;

import common.messages.KVMessage;
import common.messages.KVMessage.MessageType;


/**
 * Represents a connection end point for a particular client that is 
 * connected to the server. This class is responsible for message reception 
 * and sending. 
 * The class also implements the echo functionality. Thus whenever a message 
 * is received it is going to be echoed back to the client.
 */
public class ClientConnection implements Runnable, IResponse {

	private static Logger logger = LoggingManager.CreateServerLogger(ClientConnection.class);
	
	private boolean isOpen;
	private static final int BUFFER_SIZE = 1024;
	private static final int DROP_SIZE = 128 * BUFFER_SIZE;
	
	private Socket clientSocket;
	private InputStream input;
	private OutputStream output;
	private CommunicationListener listener;
	
	private MarshallerKvMessage marshaller;
	/**
	 * Constructs a new CientConnection object for a given TCP socket.
	 * @param clientSocket the Socket object for the client connection.
	 * @throws JAXBException 
	 */
	public ClientConnection(Socket clientSocket, CommunicationListener listener) throws JAXBException {
		this.clientSocket = clientSocket;
		this.isOpen = true;
		this.listener = listener;	
		this.marshaller = new MarshallerKvMessage();
	}
	
	/**
	 * Initializes and starts the client connection. 
	 * Loops until the connection is closed or aborted by the client.
	 */
	public void run() {
		try {
			output = clientSocket.getOutputStream();
			input = clientSocket.getInputStream();
			
			String welcomeMessage = "Connection to MSRG Echo server established: " 
					+ clientSocket.getLocalAddress() + " / "
					+ clientSocket.getLocalPort();
			this.sendResponse(new KVMessageImp(MessageType.INFO, welcomeMessage));
			System.out.println("Client was connected from "+ clientSocket.getLocalAddress()+ " : "+ clientSocket.getLocalPort());
			while(isOpen) {
				try {
					KVMessage latestMsg = this.receiveMessage();		
					// we can notify a set of listeners. But in this case
					// the only listener is the server. So for simplicity purposes
					// we only have a listener instead of a collection of listeners.
					this.listener.notifyMessage(latestMsg, this);					
				/* connection either terminated by the client or lost due to 
				 * network problems*/	
				} catch (IOException ioe) {
					logger.error("Error! Connection lost!");
					System.out.println("Error! Connection lost!");
					isOpen = false;
				}				
			}
			
		} catch (IOException ioe) {
			logger.error("Error! Connection could not be established!", ioe);
			System.out.println("Connection could not be established");
		} finally {
			
			System.out.println("Client was disconnected");
			try {
				if (clientSocket != null) {
					input.close();
					output.close();
					clientSocket.close();
					listener = null;
				}
			} catch (IOException ioe) {
				logger.error("Error! Unable to tear down connection!", ioe);
			}
		}
	}
	
	/**
	 * Method sends a TextMessage using this socket.
	 * @param msg the message that is to be sent.
	 * @throws IOException some I/O error regarding the output stream 
	 */
	public void send(String msg) throws IOException {
		byte[] msgBytes = msg.getBytes();
		send(msgBytes);
		logger.info("SEND \t<" 
				+ clientSocket.getInetAddress().getHostAddress() + ":" 
				+ clientSocket.getPort() + ">: '" 
				+ msg +"'");
    }
	
	public void send(byte[] msgBytes) throws IOException{
		output.write(msgBytes, 0, msgBytes.length);
		output.flush();
	}
	
	private KVMessage receiveMessage() throws IOException {
		logger.info("Reading bytes");
		KVMessage msg = new KVMessageImp(MessageType.UNKNOWN, "");
		//int bytesAvailable = this.myInputStream.available();
		byte[] temp = new byte[1024];
		// Read bytes
		int bytesAvailable = this.input.read(temp); 
		if (bytesAvailable != -1) {
			byte [] message = new byte [bytesAvailable];
			System.arraycopy(temp, 0, message, 0, bytesAvailable);
			/* build final String */
			msg = this.marshaller.getKVMessage(message);
			logger.info("RECEIVE \t<" 
					+ clientSocket.getInetAddress().getHostAddress() + ":" 
					+ clientSocket.getPort() + ">: '" 
					+ msg + "'");
		} else {
			this.isOpen = false;
		}
			
		return msg;
	}
	
	@Override
	public void sendResponse(KVMessage responseMessage) {		
		Exception ex = null;
		try {			
			this.send(this.marshaller.getBytes(responseMessage));				
		} catch (IOException e) {
			ex = e;
		} 					
		
		if (ex != null){
			logger.error("Error! Connection lost!");
			isOpen = false;
		}
	}
}